/*
Creation date:	from 29/04/2003 to 01/04/2003
Autor:			Mountaniol Vsevolod <sebastian@vboxcomm.com>
File:			updater.c
Description:	Create updates for XLT / XR1 systems. 
                Also apply update on target machines.
*/

#include "updater.types.h"
#include "updater.h"
#include "updater.utils.h"
#include "updater.errors.h"
#include "updater.w.h"
#include "updater.w.string.h"
#include "updater.w.file.h"
#include "updater.w.memory.h"

/*
    FUNCTION:	ConstructName
    IN:			CHAR * file_name_in, CHAR flag
    OUT:		CHAR * 
    Get name of an input file. Construct name for output file.
    Compressed   output file has suffix '.cmp'.
    Decompressed output file has suffix '.dcp'.
 
 */
INT FindFile (ucore_t * ucore, CHAR * file_name) {

  file_list_t * file_list_copy = ucore->file_list;
  /* INT strlen1, strlen2; */

  ENTRY();
  
  if (file_list_copy == NULL || file_name == NULL) {
	PRINT("RETURN error\n");
	RETURN( - EINVAL );
  }
  
  while (file_list_copy != NULL) {

	DD("\n|%s||%s|\n",file_list_copy->file_name,file_name);

	if (w_strcmp(file_list_copy->file_name, file_name) == 0) {
	  DD("Found!\n");
	  RETURN( 0 );
	}

	file_list_copy = file_list_copy->next;
  }

  RETURN( - 1 );

}



CHAR * ConstructName (CHAR * pc_file_name_in, CHAR flag) {

    CHAR * pc_file_name_out;

	ENTRY();
	
    if ( NULL == pc_file_name_in )
    {
        RETURN( NULL );
    }


    pc_file_name_out = (CHAR *) w_malloc(w_strlen(pc_file_name_in) + 5);

    if ( NULL == pc_file_name_out )
    {
        PRINT("Low memory\n");
        RETURN( NULL );
    }

    /*    construct out file name from source + '.gz' suffix */
    w_strcpy(pc_file_name_out, pc_file_name_in);

    if ( F_COMPRESS == flag )
    {
        w_strcat(pc_file_name_out, ".cmp");
    }

    if ( F_DECOMPRESS == flag )
    { 
        w_strcat(pc_file_name_out, ".dcp");
    }

    DD("constructed: %s\n", pc_file_name_out);
    RETURN( pc_file_name_out );

}


INT DestroyFileList(ucore_t * ps_core) {
    file_list_t * ps_file_list_next = ps_core->file_list;

	ENTRY();
	
    if ( NULL == ps_core || NULL == ps_core->file_list )
    {
        RETURN( - EINVAL );
    } 

    while ( NULL != ps_core->file_list )
    { 
        if ( NULL != ps_core->file_list->file_name )
        {
            w_free(ps_core->file_list->file_name);
         }
        
        if ( NULL != ps_core->file_list->path_to_install)
        {
            w_free( ps_core->file_list->path_to_install );
         }

        ps_file_list_next = ps_core->file_list->next;
        if ( NULL != ps_core->file_list )
        {
            w_free(ps_core->file_list);
         }
        ps_core->file_list = ps_file_list_next;
    }

    RETURN( OK );
}


/*
FUNCTION: 	AddFile
IN:			struct flist * fl, CHAR * file_name
OUT:		struct flist *

Get pointer to struct flist (means: file list) and CHAR * file name.
Parse string 'file name'. Add file name to list.
 
 */

INT AddFile(ucore_t * ps_ucore, CHAR * pc_file_name, ULONG ul_flags)
{
    file_list_t  * ps_fl_copy, * ps_new_node;
    CHAR * pc_f_name, *pc_f_install;

	ENTRY();


    /* TOBE paranoid, he-he! */
    if ( NULL == pc_file_name )
    { 
        RETURN( - EARGUMENT );
    }

    pc_f_name = pc_file_name;

    pc_f_install = w_strchr(pc_file_name, ' ');
    if ( NULL == pc_f_install )
    { 
        pc_f_install = w_strchr(pc_file_name, '\t');
    }

    /* found a name to be installed with */
    if ( NULL != pc_f_install && pc_f_install != pc_file_name )
    { 

        /* let's separate original name and install one: all whitespace turn to be '\0' */
        while ( ' ' == *pc_f_install  || '\t' == * pc_f_install )
         {
            *pc_f_install = '\0';
            pc_f_install++;
        }

        /*	Is there remain anything? */
        if ( w_strlen(pc_f_install) < 1 )
        {
            DD("pc_f_install: NULL\n");
            pc_f_install = NULL;
         } 
		else
        {
            DD("pc_f_install:%s\n",pc_f_install);
         }

    }

    /* create new node */

    ps_new_node= (file_list_t *) w_malloc(sizeof(file_list_t));
    if ( NULL == ps_new_node )
    {
        RETURN( - EMEMORY );
    } 

    w_memset(ps_new_node, 0, sizeof(file_list_t));

    ps_new_node->file_name = w_strdup(pc_f_name);
    if ( NULL != pc_f_install )
    {
        ps_new_node->path_to_install = w_strdup(pc_f_install);
    } 

    DD("AddFile: FLAGS: %X\n",(unsigned) ul_flags);
    ps_new_node->next = NULL;
    ps_new_node->flags = ul_flags;

    if ( NULL == ps_ucore->file_list)
    {
        ps_ucore->file_list = ps_new_node;
		RETURN( OK );
    } else 
    {
	    ps_fl_copy = ps_ucore->file_list;
        while ( NULL != ps_fl_copy->next )
        {
            ps_fl_copy = ps_fl_copy->next;
         }

        ps_fl_copy->next = ps_new_node;
        RETURN( OK );
    } 
    RETURN( - ERR );
}

/* Empty I mean contain only spaces or null length */
/* Function return 0 if string is empty. */
/* Else it returns position of first found character falls into "not space" */
/*   definition */

static INT IsStringEmpty(CHAR * string) 
{
    INT i;
    INT len = w_strlen(string);

	ENTRY();

    if ( 0 == len )
    {
        RETURN( 0 );
    } 

    for ( i = 0 ; i < len ; i++ )
    {
        if ( 0 == w_isblank(string [len]) )
        {
            RETURN((i + 1));
         }

    } 

    RETURN( 0 );
}


/*
FUNCTION: 	RemoveFlags(CHAR * string)
IN:			string
OUT:		string cleaned from flag names
*/

static CHAR * RemoveFlags(CHAR * pc_string) 
{
    CHAR * pc_separated_buf, * pc_buf;
    CHAR flag_not_empty = 0;

    CHAR * st_begin;
    CHAR *st = w_strdup(pc_string);

	ENTRY();

    pc_separated_buf = pc_buf = NULL;

    if ( NULL ==  st )
    {
        RETURN( NULL );
    } 

    st_begin = st;

	/* TODO - use buff size from user */
    pc_buf = w_malloc(DEFAULT_BUFF_BIG);
    w_memset (pc_buf, 0, DEFAULT_BUFF_BIG);

    if ( NULL == pc_buf )
    {
        RETURN( NULL );
    } 

    do 
    {
        pc_separated_buf = w_strsep(&st ," ");
        if ( NULL == pc_separated_buf )
        {
            continue;
         }
        if ( IsStringEmpty(pc_separated_buf)  
             && 0 != w_strcmp(pc_separated_buf, UPD_INSTALL_PATH_NAME) 
             && 0 != w_strcmp(pc_separated_buf, UPD_INSTALL_FORCE_NAME) 
             && 0 != w_strcmp(pc_separated_buf, UPD_INSTALL_TARGET_NAME) 
             && 0 != w_strcmp(pc_separated_buf, UPD_BACKUP_NAME)
           )
        {
            /* It looks very stupid. I use this flag to test that I not at begin of buffer. */
            /* TODO - remake it, yeah? */

            if ( flag_not_empty )
            {
                w_strcat (pc_buf, " ");
             } else
            { 
                flag_not_empty = 1;
            }

            w_strcat (pc_buf, pc_separated_buf);
        } 


    }  while ( pc_separated_buf );

    /* 
    PRINT("got    string |%s|\n", pc_string);
    PRINT("return string |%s|\n", pc_buf);
    */ 
    w_free(st_begin);
    RETURN(pc_buf);

}

/*
    FUNCTION:	CutTail
    IN:			CHAR *
    OUT:		INT
    Get null terminated string. Cut all symbols
    that aren't numbers or alphabet.
    Return OK (0) on success or error.
 
 */
static INT CutTail(CHAR * pc_string)
{
	CHAR * pc_tail;

	ENTRY();
	/*	Test the string validity */
	if (NULL == pc_string)
	{
		RETURN( - ESTRING );
	} 
	
	pc_tail = pc_string;

	/*	Go to the end of the string */
	while ('\0' != *pc_tail)
	{
		pc_tail++;
	} 

	/* If tail is same as begin - this is null length string */
	if (pc_tail == pc_string)
	{ 
		RETURN( OK );
	}

	/* Go back and change every char that isn't alphanum with '\0' */
	while ( (pc_tail >= pc_string ) && ( ! w_isalnum(*pc_tail) ) )
	{ 
		*pc_tail = '\0';
		pc_tail--;
	}

	RETURN( OK );

}

/*
    FUNCTION:	ReadSpec
    IN:			CHAR *
    OUT:		stryct flist *
    Get name of spec file.
    Read spec file, split lines, cut '\n'.
    Add file names into linked list `flist`.
    Return linked list of file names.
    All lines begin with '#' are comments.
 
 */
#if JOPA
INT  ReadSpec(ucore_t * ps_ucore, CHAR * pc_file_name)
{
    FD_T * ps_fd;
    CHAR * pc_buf;
    CHAR * pc_buf_p;
    CHAR * pc_buf_clean = NULL;
    ULONG  flags;

	ENTRY();

    if ( NULL == pc_file_name )
    { 
        RETURN( - EARGUMENT );
    }

    /*	open config file */
    ps_fd = w_open(pc_file_name, W_O_RDONLY);
    if ( NULL == ps_fd )
    { 
        PERROR("Can't open spec");
        RETURN( - EFILEOPEN );
    }

	/* TODO - use buff size from user */
    pc_buf = w_malloc (DEFAULT_BUFF_BIG);

    if ( NULL == pc_buf )
    {
        w_close(ps_fd);
        PERROR("Can't allocate memory");
        RETURN( - EMEMORY );
    } 

    do
    {
        pc_buf_p = pc_buf; 

        /*	clean buffer */
		/* TODO - use buff size from user */
        w_memset (pc_buf, 0, DEFAULT_BUFF_BIG);

        /*	read string by string */
		/* TODO - use buff size from user */
		pc_buf_p = fgets(pc_buf, DEFAULT_BUFF_BIG, ps_fd); 

        DD("from fgets: %s\n", pc_buf_p);
        /*	did it read anything? */
        /* if ( rv != pc_buf_p ) */
        if ( NULL == pc_buf_p )
        {
            continue;
        }

        /*	split all tabs, spaces, etc */
        while ( ' ' == *pc_buf_p || '\t' == *pc_buf_p )
        {
            pc_buf_p++;
        }

        /* 	check if line commented or not */
        if ( '#' == *pc_buf_p )
        {
            continue;
        }

        /*	check if the line is command line */
        if ( ':' == *pc_buf_p )
        {
            /*TODO*/
            continue;
        }

        /*	check end of line - if I need cut it off ? */
        DD("Before cut: %s\n", pc_buf_p);
		if (OK != CutTail(pc_buf_p))
		{ 
            w_close(ps_fd);
			RETURN( ECANCELED );
		}
        DD("After cut: %s\n", pc_buf_p);
        
        /*	is there remain something at all? :-) */
        if ( 0 == w_strlen (pc_buf_p) )
        {
            continue;
        } 

        /*	if `fl` isn't uninitialized yet */
        if ( NULL == ps_ucore->file_list )
        { 
            flags = ExtractFlags(pc_buf_p);
            TFREE(pc_buf_clean);
            pc_buf_clean = RemoveFlags(pc_buf_p);

            if ( AddFile(ps_ucore, pc_buf_clean, flags) )
             {
                PERROR("error - can't add first file name to list");
                w_free(pc_buf);
                w_close(ps_fd);
                RETURN( ECANCELED );
            }
        }
        /*	else if it is */
        else
        {
            flags = ExtractFlags(pc_buf_p);
            TFREE(pc_buf_clean);
            pc_buf_clean = RemoveFlags(pc_buf_p);
            if ( AddFile(ps_ucore, pc_buf_clean, flags) )
            {
                PERROR("error - can't add file name to list");
                TFREE(pc_buf_clean);
                w_free(pc_buf);
                w_close(ps_fd);
                RETURN( ECANCELED );
             }
         }

    }  while ( NULL != pc_buf_p );

    w_close(ps_fd);
    TFREE(pc_buf);
    TFREE(pc_buf_clean);
    RETURN( OK );
}

#endif /* JOPA */

#if defined(DEBUG)
/*
    FUNCTION:	PrintFileList
    IN:			struct flist *
    OUT:		nothing

    Get linked list of file names.
    Print file names.
    For debug only.
 */

#if 0
void PrintFileList(struct flist *fl) {
    struct flist * fl_copy = fl;

	ENTRY();
	
    if ( NULL == fl_copy )
    { 
        PRINT("wrong argument\n");
        RETURNVOID();
    }
    while ( NULL != fl_copy )
    {
        PRINT("file: %s\n",fl_copy->file_name);
        fl_copy = fl_copy->next;
    } 

	RETURNVOID();

}
#endif
#endif /* DEBUG */

/*
    FUNCTION: 	ValidateFiles
    IN:  		struct flist *
    OUT: 		INT

    Get linked list of file names. Try to `stat()` every file.
    If any of these can't be `stat()` return error immediatly.
 
 */
INT ValidateFiles(ucore_t * ps_ucore) {
	file_list_t * fl;
    STAT_T * ps_st;
    INT rv;

	ENTRY();
	
	fl = ps_ucore->file_list;
	
    if ( NULL ==  fl )
    { 

        PRINT("wrong argument\n");
        RETURN( - EINVAL );
    }

    ps_st = w_new_stat();
    if ( NULL == ps_st )
    {
        RETURN( - ENOMEM );
    }


    /*	go node by node */
    while ( NULL != fl )
    {
        DD("validate file: %s\n",fl->file_name);

        /*	try `stat() file` */
        rv = w_stat(fl->file_name, ps_st);
        if ( 0 != rv )
        {
            switch ( errno )
            {
            case EBADF :
                PRINT("filedes is bad.\n");
                break;
            case ENOENT :
                PRINT("A component of the path %s does not exist, or the path is an empty string.\n", fl->file_name);
                break;
            case ENOTDIR :
                PRINT("A component of the path is not a directory.\n");
                break;
            case ELOOP :
                PRINT("Too many symbolic links encountered while traversing the path.\n");
                break;
            case EFAULT :
                PRINT("Bad address\n");
                break;
            case EACCES :
                PRINT("Permission denied\n");
                break;
            case ENOMEM :
                PRINT("Out of memory (i.e. kernel memory)\n");
                break;
            case ENAMETOOLONG :
                PRINT("File name too long\n");
                break;
            }

            w_free_stat(ps_st);
            RETURN( rv );
        }

        /*	Is it the file regular file? */


        if ( w_stat_isdir(ps_st) )
        {
            PRINT("File %s is directory: empty directory not supported.\n", fl->file_name);
            w_free_stat(ps_st);
            RETURN( - ENOTSUP );
        }

        if ( w_stat_ischr(ps_st) )
         {
            PRINT("File %s is character device: not supported by updater.\n", fl->file_name);
            RETURN( - ENOTSUP );
        }

        if ( w_stat_isblk(ps_st) )
         {
            PRINT("File %s is block device: not supported by updater.\n", fl->file_name);
            RETURN( - ENOTSUP );
        }

        if ( w_stat_isfifo(ps_st) )
        {
            PRINT("File %s is fifo: not supported by updater.\n", fl->file_name);
            RETURN( - ENOTSUP );
         }

        if ( w_stat_issock(ps_st) )
        {
            PRINT("File %s is socket: not supported by updater.\n", fl->file_name);
            RETURN( - ENOTSUP );
        } 



        if ( ! w_stat_isreg(ps_st) )
        {
            PRINT("File %s is not regular file.\n", fl->file_name);
            RETURN( - ENOTSUP );
        } 

        fl = fl->next;
    } 

    RETURN( OK );
}



/*
    FUNCTION: 	ExtractDirName
    IN:			CHAR *
    OUT:		CHAR *
    Get file name with path. Extract and return  path.
*/
INT ExtractDirName (CHAR * dir_name, const CHAR *pc_file_name ) {
    INT  end_of_string;
	ENTRY();
    if ( NULL == pc_file_name || 0 == w_strlen(pc_file_name) )
    { 
/*	PERROR("1031 pc_file_name is NULL");	*/
        RETURN( -1 );
    }
/*  DD("Got: %s\n", pc_file_name); */
    w_strcpy(dir_name, pc_file_name);
    if ( NULL == dir_name )
    { 
        RETURN( -1 );
    }

/*  w_strncpy(pc_buf, pc_file_name, FH_PATH_SIZE); */
    end_of_string = w_strlen(dir_name) - 1;

    for ( ; end_of_string >= 0; end_of_string-- )
    { 

        if ( '/' == dir_name[end_of_string] )
        {
            dir_name[end_of_string] = '\0';
            RETURN( 0 );
         }

        dir_name[end_of_string] = '\0';

    } 

/*  DD("1034 return NULL\n"); */
    RETURN( 0 );

}

/*
FUNCTION: 	CreatePath
IN:			CHAR *
OUT:		INT
Get directory name. Return 0 if success and not 0 on error.
If directory exists - success.
If not exists - try create it. 
If created OK return success.
TODO: permissions!
 
 */

INT CreatePath (CHAR * dir) {
    VOID *dirp;
    INT rv;
    CHAR * dirname;

	ENTRY();

    if ( NULL == dir )
    {
        RETURN( - EINVAL );
    } 

    if ( 0 == w_strlen(dir) )
    {
        RETURN( 0 );
    } 

    DD("1055 Got dir name: %s\n",dir);

    /*	try to open directory */
    dirp = w_opendir(dir);

    /*	if can't open try to create dir */
    if ( NULL != dirp )
    { 
        w_closedir (dirp);
        RETURN( 0 );
    }

    /* Allocate memory for extracter directory name: size of the string + 1 */
    dirname = w_malloc (w_strlen(dir) * sizeof(CHAR) + sizeof(CHAR));
    if (NULL == dirname) {
        RETURN(- 1);
    }

    rv = ExtractDirName( dirname, dir );

    if (rv)  {
        RETURN(-1);
    }
    
    rv = CreatePath (dirname);

    if ( 0 == rv )
    {
        /*	if can't create dir it seems to be multilayer path*/
        DD("try create:%s\n",dir);
        rv = w_mkdir(dir , W_S_IRWXU);
     } 

    w_free(dirname);

    RETURN( rv );
}


/* 
FUNCTION: 	ExtractFlags(CHAR * string)
IN:			string from spec file
OUT:		ULONG contains all flags described in the string.
*/
ULONG ExtractFlags(CHAR * string)
{
    ULONG flags = 0;
    CHAR *b;
    CHAR *st_begin;
    CHAR *st =  w_strdup(string);

	ENTRY();

    if ( 0 == st )
    { 
        RETURN( 0 );
    }

    st_begin = st;

    do
    {
        b = w_strsep(&st, " ");
        if ( NULL != b )
        { 

            if ( 0 == w_strcmp(b, UPD_INSTALL_PATH_NAME) )
            {
                SET_FLAG(flags, UPD_INSTALL_PATH);
                PRINT("Set flag: UPD_INSTALL_PATH\n");
            } 

            if ( 0 == w_strcmp(b, UPD_INSTALL_FORCE_NAME) )
            {
                SET_FLAG(flags, UPD_INSTALL_FORCE);
                PRINT("Set flag: UPD_INSTALL_FORCE\n");
            } 

            if ( 0 == w_strcmp(b, UPD_INSTALL_TARGET_NAME) )
            {
                SET_FLAG(flags, UPD_INSTALL_TARGET);
                PRINT("Set flag: UPD_INSTALL_TARGET\n");
            } 

            if ( 0 == w_strcmp(b, UPD_BACKUP_NAME) )
            {
                SET_FLAG(flags, UPD_BACKUP);
                PRINT("Set flag: UPD_BACKUP\n");
            } 

        }
    }  while ( NULL != b );

    w_free(st_begin);

    RETURN( flags );
}



INT AllocateInOutBuffers(ucore_t * ps_ucore, ULONG ul_in_buffer_size, ULONG ul_out_buffer_size)
{
    ENTRY();

    /* Allocate IN buffer. */
    ps_ucore->buffers.buffer_in = (VOID *) w_malloc(ul_in_buffer_size);
    if (NULL == ps_ucore->buffers.buffer_in)
    {
        PERROR("Cannot allocate input buffer: ");
        RETURN(-1);
    } 


    /* Allocate OUT buffer. */
    ps_ucore->buffers.buffer_out = (VOID *) w_malloc(ul_out_buffer_size);
    if (NULL == ps_ucore->buffers.buffer_out)
    { 
        PERROR("Cannot allocate output buffer: ");
        goto error_1;       
    }

        /* Init OUT buffer size */
    ps_ucore->buffers.size_out = (ULONG *) w_malloc(sizeof(LONG));
    if (NULL == ps_ucore->buffers.size_out) {
        PERROR("Couldn't allocate size_out var");
        goto error_2;
    } 

    *(ps_ucore->buffers.size_out) = ul_out_buffer_size;

    /* Init IN buffer size */
    ps_ucore->buffers.size_in = (ULONG *) w_malloc(sizeof(LONG));
    if (NULL == ps_ucore->buffers.size_out) {
        PERROR("Couldn't allocate size_in var");
        goto error_3;
    } 

    *(ps_ucore->buffers.size_in) = ul_in_buffer_size;

    RETURN(0);

error_3:
    w_free(ps_ucore->buffers.size_out);
error_2:
    w_free(ps_ucore->buffers.buffer_out);
error_1:
    w_free(ps_ucore->buffers.buffer_in);
    
    RETURN(-1);
}

